package org.nlp2rdf.navigator.refactored;

import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.sparql.resultset.ResultSetRewindable;
import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;
import org.apache.log4j.Logger;
import org.dllearner.kb.sparql.SparqlQueryDescriptionConvertVisitor;
import org.dllearner.parser.ParseException;
import org.nlp2rdf.navigator.client.common.ClassificationCountResultDTO;
import org.nlp2rdf.navigator.client.common.ClassificationResultDTO;
import org.nlp2rdf.navigator.client.common.ExampleDTO;
import org.nlp2rdf.navigator.client.exception.NKEConceptParseException;
import org.nlp2rdf.navigator.client.exception.NKEQueryException;

import java.io.BufferedInputStream;
import java.io.ObjectInputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

public class SparqlClassifier implements IClassifier {
    private static final Logger logger = Logger.getLogger(SparqlClassifier.class);

    private String ruleGraph = null;
    private boolean retrieveLabels = false;
    private String customFilter = "";
    private Map<String, Set<String>> subclassMap = null;

    private final ISparqlService sparqlService;

    public SparqlClassifier(ISparqlService sparqlService) {
        this(sparqlService, null);
    }

    public SparqlClassifier(ISparqlService sparqlService, org.springframework.core.io.Resource subclassMapResource) {
        this.sparqlService = sparqlService;
        if (subclassMapResource != null) {
            subclassMap = readObjectFromResource(subclassMapResource);
        } else {
            subclassMap = null;
        }

    }

    public Map<String, Set<String>> readObjectFromResource(org.springframework.core.io.Resource r) {
        ObjectInputStream ois = null;
        try {
            BufferedInputStream bis = new BufferedInputStream(r.getInputStream());
            ois = new ObjectInputStream(bis);
            return (Map<String, Set<String>>) ois.readObject();
        } catch (Exception e) {
            logger.error("loading resource failed", e);
        } finally {
            try {
                ois.close();
            } catch (Exception e) {
                logger.error("", e);
            }

        }
        return null;
    }


    /**
     * passes arguments to SparqlQueryDescriptionConvertVisitor
     *
     * @param conceptInKBSyntax
     * @param count
     * @param limit
     * @param offset
     * @return
     * @throws NKEConceptParseException
     */
    private String _getSparqlQuery(String conceptInKBSyntax, boolean count, int limit, int offset)
            throws NKEConceptParseException {
        String sparqlQuery = "";

        try {
            SparqlQueryDescriptionConvertVisitor visit = new SparqlQueryDescriptionConvertVisitor();
            visit.setDistinct(true);
            visit.setLabels(retrieveLabels);
            visit.setLimit(limit);
            visit.setCount(count);
            visit.setOffset(offset);
            visit.setCustomFilter(customFilter);

            if (subclassMap != null) {
                visit.setSubclassMap(subclassMap);
            }
            sparqlQuery = visit.getSparqlQuery(conceptInKBSyntax);
            String ruleGraphTmp = (this.ruleGraph == null) ? "" : " \n define input:inference \""
                    + this.ruleGraph + "\" \n";
            sparqlQuery = ruleGraphTmp + sparqlQuery;
        } catch (ParseException e) {
            String msg = "Parsing the concept failed\n";
            logger.error(msg, e);
            throw new NKEConceptParseException(msg);
        }
        logger.trace("Converted concept: \n" + sparqlQuery);
        return fixIt(sparqlQuery);
    }

    @Deprecated
    private String fixIt(String s) {
        String pattern = "\\.\\s+\\.";
        if (Pattern.compile(pattern).matcher(s).find()) {
            String test = s;
            logger.warn("problem with query\n it has two points, patching  it now\n " + s + "");
            test = test.replaceAll(pattern, ".");
            return test;
        }
        return s;

    }

    /*
      * (non-Javadoc)
      *
      * @see
      * org.nlp2rdf.navigator.refactored.IClassifier#classifyCount(java
      * .lang.String)
      */
    public ClassificationCountResultDTO classifyCount(String conceptInKBSyntax)
            throws NKEConceptParseException, NKEQueryException {
        Monitor m = MonitorFactory.getTimeMonitor("classify.count").start();
        conceptInKBSyntax = quickFix(conceptInKBSyntax);

        String sparqlQuery = _getSparqlQuery(conceptInKBSyntax, true, 0, 0);
        logger.debug("converted count query: " + sparqlQuery);

        ClassificationCountResultDTO ret = new ClassificationCountResultDTO();
        ret.setCount(sparqlService.queryAsCount(sparqlQuery));
        m.stop();
        ret.setMsNeeded(Math.round(m.getLastValue()));
        return ret;
    }

    /*
      * (non-Javadoc)
      *
      * @see org.nlp2rdf.navigator.server.IClassifier#classify(java.lang.String,
      * java.util.Map, int, int)
      */
    public ClassificationResultDTO classify(String conceptInKBSyntax, int limit, int offset)
            throws NKEConceptParseException, NKEQueryException {
        Monitor m = MonitorFactory.getTimeMonitor("classify.retrieve").start();
        conceptInKBSyntax = quickFix(conceptInKBSyntax);

        String sparqlQuery = _getSparqlQuery(conceptInKBSyntax, false, limit, offset);
        logger.debug("converted classify query: " + sparqlQuery);

        List<String> exampleURIs = new ArrayList<String>();

        ResultSetRewindable rsw = sparqlService.queryAsResultSet(sparqlQuery);
        // TODO might not always work
        while (rsw.hasNext()) {
            QuerySolution qs = rsw.nextSolution();
            RDFNode instance = qs.get("subject");
            if (instance.isURIResource()) {
                exampleURIs.add(((Resource) instance).getURI());
            } else if (instance.isLiteral()) {
                String uri = ((Literal) instance).getLexicalForm();
                try {
                    URI u = new URI(uri);
                    exampleURIs.add(u.toString());
                } catch (URISyntaxException e) {
                    logger.warn("classify retrieved strings instead of resource, which where not uris: "
                            + instance + "\n" + sparqlQuery, e);
                }
            } else {
                logger.warn("classify retrieved something strange: " + sparqlQuery);
            }
        }

        ClassificationResultDTO ret = new ClassificationResultDTO();
        List<ExampleDTO> retrievedExamples = new ArrayList<ExampleDTO>();
        ExampleDTO oneExample;
        for (String exUri : exampleURIs) {
            oneExample = new ExampleDTO();
            oneExample.setUri(exUri);
            retrievedExamples.add(oneExample);
        }
        ret.setRetrievedExamples(retrievedExamples);
        logger.debug("Retrieved " + retrievedExamples.size() + " new examples");
        m.stop();
        ret.setMsNeeded(Math.round(m.getLastValue()));
        return ret;
    }

    @Deprecated
    private String quickFix(String in) {
        return in.replaceAll("hasValue", "HASVALUE");
    }

    public void setRuleGraph(String ruleGraph) {
        this.ruleGraph = ruleGraph;
    }


    public void setCustomFilter(String customFilter) {
        this.customFilter = customFilter;
    }


}
